---
id: unit1
title: 第一章  
---

### React Hook 

React Hook 是一种函数，它允许开发者在函数组件中使用状态和其他 React 特性。它在 React 版本 16.8 中被引入，旨在简化和统一 React 组件的状态管理和生命周期方法。

在 React Hook 之前，React 的状态管理主要通过类组件和它们的生命周期方法来完成。然而，类组件可能会很复杂，尤其是当处理复杂的状态管理和副作用时更是如此。

- 使用 React Hook，开发者可以使用简单的、可组合的函数在函数组件内部管理状态和副作用。其中一些最常用的 Hook 包括：
    - useState：允许组件在不使用类组件的情况下管理状态。它返回一个有状态的值和一个更新它的函数。
    - useEffect：允许组件在渲染后处理副作用，例如获取数据或操作 DOM。它类似于类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期方法。
    - useContext：允许组件访问由提供程序创建的上下文对象。这可用于在不必通过每个中间组件传递它们的情况下将数据和函数向下传递组件树。

React Hook 因其简单性和易用性越来越受到 React 开发者的青睐。它们还使编写可重用、可组合的代码变得更加容易，因为开发者可以将逻辑提取到自定义 Hook 中，这些 Hook 可以被用于多个组件。


## 不支持 IE11 浏览器

react18 引入的新特性全部基于现代浏览器，如需支持需要退回到 react17 版本

## setState 

### 什么时候是异步的，同步的?
取决于它的调用方式和调用时机。
- 在事件处理函数、生命周期函数和异步回调函数中被调用时，它是异步的。这是因为 React 会将多个状态更新合并为一个批量更新，以提高性能。这种情况下，setState() 并不会立即更新状态，而是将更新放入一个队列中，在适当的时候才会执行更新。由于 setState() 是异步的，所以在调用后不能立即获得最新的状态值。
  - 对同一个值进行多次 setState， setState 的批量更新策略会对其进行覆盖，取最后一次的执行结果。
  - 如果是同时 setState 多个不同的值，在更新时会对其进行合并后进行批量更新。
- 在 React 的批量更新外部被调用时，它是同步的。例如，在 componentDidMount()、componentDidUpdate() 或其他非异步上下文中调用 setState() 方法时，React 会立即更新组件的状态，而不会将更新放入队列中。这种情况下，setState() 是同步的，可以立即获得最新的状态值。


在 react17 中，只有 react 事件会进行批处理，原生 js 事件、promise，setTimeout、setInterval 不会  
react18，将所有事件都进行批处理，即多次 setState 会被合并为 1 次执行，提高了性能，在数据层，将多个状态更新合并成一次处理（在视图层，将多次渲染合并成一次渲染）

## react 组件返回值更新

- 在 react17 中，返回空组件只能返回 null，显式返回 undefined 会报错
- 在 react18 中，支持 null 和 undefined 返回

## strict mode 更新

当你使用严格模式时，React 会对每个组件返回两次渲染，以便你观察一些意想不到的结果,在 react17 中去掉了一次渲染的控制台日志，以便让日志容易阅读。react18 取消了这个限制，第二次渲染会以浅灰色出现在控制台日志。

## Suspense 不再需要 fallback 捕获

## 支持 useId

在服务器和客户端生成相同的唯一一个 id，避免 hydrating 的不兼容

## useSyncExternalStore

用于解决外部数据撕裂问题

## useInsertionEffect

这个 hooks 只建议在 css in js 库中使用，这个 hooks 执行时机在 DOM 生成之后，useLayoutEffect 执行之前，它的工作原理大致与 useLayoutEffect 相同，此时无法访问 DOM 节点的引用，一般用于提前注入脚本。

## Concurrent Mode

并发模式不是一个功能，而是一个底层设计。  
它可以帮助应用保持响应，根据用户的设备性能和网速进行调整，它通过渲染可中断来修复阻塞渲染机制。在 concurrent 模式中，React 可以同时更新多个状态  
区别就是使同步不可中断更新变成了异步可中断更新  
useDeferredValue 和 startTransition 用来标记一次非紧急更新

## 为什么在文件中没有使用 react，也要在文件顶部 import React from “react”

只要使用了 jsx，就需要引用 react，因为 jsx 本质就是 React.createElement

## 为什么 React 自定义组件首字母要大写

jsx 通过 babel 转义时，调用了 React.createElement 函数，它接收三个参数，分别是 type 元素类型，props 元素属性，children 子元素。  
如下图所示，从 jsx 到真实 DOM 需要经历 jsx->虚拟 DOM->真实 DOM。如果组件首字母为小写，它会被当成字符串进行传递，在创建虚拟 DOM 的时候，就会把它当成一个 html 标签，而 html 没有 app 这个标签，就会报错。组件首字母为大写，它会当成一个变量进行传递，React 知道它是个自定义组件就不会报错了

```js
<app>lyllovelemon</app>
// 转义后
React.createElement("app",null,"lyllovelemon")

<App>lyllovelemon</App>
// 转义后
React.createElement(App,null,lyllovelemon)
```

## React 组件为什么不能返回多个元素

- React 组件最后会编译为 render 函数，函数的返回值只能是 1 个，如果不用单独的根节点包裹，就会并列返回多个值，这在 js 中是不允许的。

```js
class App extends React.Component{
  render(){
    return(
    <div>
     <h1 className="title">lyllovelemon</h1>
      <span>内容</span>
    </div>
  )
}

//编译后
class App extends React.Component{
  render(){
    return React.createElement('div',null,[
      React.createElement('h1',{className:'title'},'lyllovelemon'),
      React.createElement('span'),null,'内容'
      ])
  }
}
```

- react 的虚拟 DOM 是一个树状结构，树的根节点只能是 1 个，如果有多个根节点，无法确认是在哪棵树上进行更新。vue 的根节点为什么只有一个也是同样的原因。

## React 组件怎样可以返回多个组件

- 使用 HOC（高阶函数）
- 使用 React.Fragment,可以让你将元素列表加到一个分组中，而且不会创建额外的节点（类似 vue 的 template）

```js
renderList(){
  this.state.list.map((item,key)=>{
    return (<React.Fragment>
      <tr key={item.id}>
        <td>{item.name}</td>
        <td>{item.age}</td>
        <td>{item.address}</td>
      </tr>
    </React.Fragment>)
  })
}

```

- 使用数组返回

```js
renderList(){
  this.state.list.map((item,key)=>{
    return [
      <tr key={item.id}>
        <td>{item.name}</td>
        <td>{item.age}</td>
        <td>{item.address}</td>
      </tr>
    ]
  })
}
```

## 引入了新的 root API，支持 new concurrent renderer(并发模式的渲染)

```js
//React 17
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

const root = document.getElementById("root");
ReactDOM.render(<App />, root);

// 卸载组件
ReactDOM.unmountComponentAtNode(root);

// React 18
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = document.getElementById("root");
ReactDOM.createRoot(root).render(<App />);

// 卸载组件
root.unmount();
```

## flushSync

```js
import React,{useState} from "react"
import {flushSync} from "react-dom"

const App=()=>{
  const [count,setCount]=useState(0)
  const [count2,setCount2]=useState(0)

  return (
    <div className="App">
      <button onClick=(()=>{
        // 第一次更新
        flushSync(()=>{
          setCount(count=>count+1)
        })
        // 第二次更新
        flushSync(()=>{
          setCount2(count2=>count2+1)
        })
      })>点击</button>
      <span>count:{count}</span>
      <span>count2:{count2}</span>
    </div>
  )
}
export default App

```

## React 的生命周期

分为挂载、更新、卸载阶段

- 挂载
  - constructor 可以进行 state 和 props 的初始化
  - static getDerivedStateFromProps
  - render
  - componentDidMount 第一次渲染后调用，可以访问 DOM，进行异步请求和定时器、消息订阅。
- 更新 (当组件的 props 或 state 变化会触发更新)
  - static getDerivedStateFromProps
  - shouldComponentUpdate 返回一个布尔值，默认返回 true，可以通过这个生命周期钩子进行性能优化，确认不需要更新组件时调用
  - render
  - getSnapShotBeforeUpdate
  - componentDidUpdate 在组件完成更新后调用
- 卸载
  - componentWillUnmount 组件从 DOM 中被移除的时候调用
- 错误捕获
  - static getDerivedStateFromError 在 errorBoundary 中使用
  - componentDidCatch

## React 事件机制

### 什么是合成事件

React 基于浏览器的事件机制实现了一套自身的事件机制，它符合 W3C 规范，包括事件触发、事件冒泡、事件捕获、事件合成和事件派发等。

### React 事件的设计动机(作用)：

- 在底层磨平不同浏览器的差异，React 实现了统一的事件机制，我们不再需要处理浏览器事件机制方面的兼容问题，在上层面向开发者暴露稳定、统一的、与原生事件相同的事件接口
- React 把握了事件机制的主动权，实现了对所有事件的中心化管控
- React 引入事件池避免垃圾回收，在事件池中获取或释放事件对象，避免频繁的创建和销毁

### React 事件机制和原生 DOM 事件流有什么区别

虽然合成事件不是原生 DOM 事件，但它包含了原生 DOM 事件的引用，可以通过 e.nativeEvent 访问

### 事件流

一个页面往往会绑定多个事件，页面接收事件的顺序叫事件流

- W3C 标准事件的传播过程： - 事件捕获 - 处于目标 - 事件冒泡
  常用的事件处理性能优化手段：事件委托

- react 事件流
  - React16 的事件绑定在 document 上
  - React17 以后事件绑定在 container 上,ReactDOM.render(app,container),而不是绑定在 DOM 元素上（作用：减少内存开销，所有的事件处理都在 container 上，其他节点没有绑定事件）
  - React 自身实现了一套冒泡机制，不能通过 return false 阻止冒泡。
  - React 通过 SytheticEvent 实现了事件合成

## 错误边界

React 部分组件的错误不应该导致整个应用崩溃，为了解决这个问题，React16 引入了错误边界

React 组件在内部定义了 getDerivedStateFromError 或者 componentDidCatch，它就是一个错误边界。两个函数的区别是前者展示降级 UI，后者记录具体的错误信息，它只能用于 class 组件

```js
import React from "react"
class ErrorBoundary extends React.Component{
  constructor(props){
    super(props)
    this.state={
      hasError:false
    }
  }
  staic getDerivedStateFromError(){
    return { hasError:true}
  }
  componentDidCatch(err,info){
    console.error(err,info)
  }
  render(){
    if(this.state.hasError){
      return <div>Oops,err</div>
    }
    return this.props.children
  }
}

// App.jsx
import React from "react"
import ErrorBoundary from "./components/ErrorBoundary"
import ComponentA from "./components/ComponentA"
export class App extends React.Component{
  render(){
    return (
      <ErrorBoundary>
        <ComponentA></ComponentA>
      </ErrorBoundary>
    )
  }
}
```

## hooks

### useState

useState 可以使函数组件像类组件一样拥有 state，函数组件通过 useState 可以让组件重新渲染，更新视图。

### useReducer

useReducer 是 react-hooks 提供的能够在无状态组件中运行的类似 redux 的功能 api 。

### useSyncExternalStore

useSyncExternalStore 的诞生并非偶然，和 v18 的更新模式下外部数据的 tearing 有着十分紧密的关联。useSyncExternalStore 能够让 React 组件在 concurrent 模式下安全地有效地读取外接数据源，在组件渲染过程中能够检测到变化，并且在数据源发生变化的时候，能够调度更新。当读取到外部状态发生了变化，会触发一个强制更新，来保证结果的一致性。

```js
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
```

① subscribe 为订阅函数，当数据改变的时候，会触发 subscribe，在 useSyncExternalStore 会通过带有记忆性的 getSnapshot 来判别数据是否发生变化，如果发生变化，那么会强制更新数据。  
② getSnapshot 可以理解成一个带有记忆功能的选择器。当 store 变化的时候，会通过 getSnapshot 生成新的状态值，这个状态值可提供给组件作为数据源使用，getSnapshot 可以检查订阅的值是否改变，改变的话那么会触发更新。  
③ getServerSnapshot 用于 hydration 模式下的 getSnapshot。

- useSyncExternalStore 基础用法：


```js 
import { combineReducers , createStore  } from 'redux'

/* number Reducer */
function numberReducer(state=1,action){
    switch (action.type){
      case 'ADD':
        return state + 1
      case 'DEL':
        return state - 1
      default:
        return state
    }
}

/* 注册reducer */
const rootReducer = combineReducers({ number:numberReducer  })
/* 创建 store */
const store = createStore(rootReducer,{ number:1  })

function Index(){
    /* 订阅外部数据源 */
    const state = useSyncExternalStore(store.subscribe,() => store.getState().number)
    console.log(state)
    return <div>
        {state}
        <button onClick={() => store.dispatch({ type:'ADD' })} >点击</button>
    </div>
}
```


### useTransition

### useDeferredValue


### useEffect

### useLayoutEffect

### useInsertionEffect

### useContext

### useRef

### useImperativeHandle

### useMemo

### useCallback